package com.tom.demo.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.tom.demo.common.constant.CacheConstants;
import com.tom.demo.common.utils.RedisUtil;
import com.tom.demo.component.TokenComponent;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

/**
 * @Author: LanQiang
 * @Date: 2023/01/17/17:24
 * @Description: 1:我们需要自定义一个过滤器，这个过滤器会去获取请求头中的token，
 * 2:对token进行解析取出其中的uuid。
 * 3:使用uuid去redis中获取对应的User对象。
 * 4:然后封装Authentication对象存入SecurityContextHolder
 * SpringSecurity后面执行的过滤器会去SecurityContextHolder中获取信息，
 * 所以在放行之前要将信息存入SecurityContextHolder。
 */
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    private TokenComponent tokenComponent;

    @Resource
    private Environment environment;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private ObjectMapper objectMapper;

    // 令牌自定义标识
    @Value("${token.header}")
    private String header;

    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest request,
                                    @NotNull HttpServletResponse response,
                                    @NotNull FilterChain filterChain) throws ServletException, IOException {
        //检测当前是否是开发环境
        String[] activeProfiles = environment.getActiveProfiles();
        if(Arrays.asList(activeProfiles).contains("dev")) {
            String authorization = request.getHeader(header);
            if(CacheConstants.GLOBAL_LOGIN_TOKEN_KEY.equals(authorization)){
                Object cacheObject = redisUtil.getCacheObject(CacheConstants.GLOBAL_LOGIN_TOKEN_KEY);
                DetailsUser detailsUser = objectMapper.convertValue(cacheObject, DetailsUser.class);
                //存入SecurityContextHolder,后面的过滤器会从SecurityContextHolder自动获取的
                //TODO 获取权限信息封装到Authentication中,security会自动和需要权限/角色的接口去对比和匹配
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(detailsUser, null, detailsUser.getAuthorities());
                //段代码的作用是将请求的详细信息（如IP地址、端口号等）附加到认证令牌中。这在Spring Security中通常用于记录额外的信息，这些信息可能对审计或日志记录有用
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                //这里我们才可以将用户认证通过的信息存储起来，方便后边使用
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                //通过了正确的token的校验，拿到id，再查询数据库之后，最后放行
                filterChain.doFilter(request, response);
                return;
            }
        }
        DetailsUser detailsUser = tokenComponent.getLoginUser(request);
        if (detailsUser == null) {
            //如果token是null,或者是'',直接放行,SpringSecurity会自己判断并且抛出异常
            filterChain.doFilter(request, response);
            return;
        }
        //存入SecurityContextHolder,后面的过滤器会从SecurityContextHolder自动获取的
        //TODO 获取权限信息封装到Authentication中,security会自动和需要权限/角色的接口去对比和匹配
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(detailsUser, null, detailsUser.getAuthorities());
        //段代码的作用是将请求的详细信息（如IP地址、端口号等）附加到认证令牌中。这在Spring Security中通常用于记录额外的信息，这些信息可能对审计或日志记录有用
        usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        //这里我们才可以将用户认证通过的信息存储起来，方便后边使用
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        //通过了正确的token的校验，拿到id，再查询数据库之后，最后放行
        filterChain.doFilter(request, response);
    }
}